/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package twitterWatchFollow;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import sun.awt.windows.ThemeReader;
import twitter4j.*;
import twitter4j.auth.AccessToken;
import twitter4j.conf.ConfigurationBuilder;
import xjcConf.MessageType;
import xjcConf.ReplyMessageType;
import xjcConf.ReplyOrderType;

/**
 *
 * @author yos
 */
public class TwitterCommon {

    public enum FriendsFollowers { FRIENDS, FOLLOWERS }
    
    public static Twitter logonByConsumerKey(String confUser, xjcConf.ApplicationType application){
        String name=application.getName();
        String consumarkey=application.getConsumerKey();
        String consumersecret=application.getConsumerSecret();
        String accesstoken=application.getAccessToken();
        String accesssecret=application.getAccessSecret();
        System.out.printf("user: %s, application: %s, consumar: %s, accesstoken: %s\n",confUser,name,consumarkey,accesstoken);
        ConfigurationBuilder confbuilder = new ConfigurationBuilder();
        confbuilder.setOAuthConsumerKey(consumarkey);
        confbuilder.setOAuthConsumerSecret(consumersecret);
        TwitterFactory twitterfactory = new TwitterFactory(confbuilder.build());
        Twitter twitter = twitterfactory.getInstance(new AccessToken(accesstoken,accesssecret));
        return twitter;   
    }
    
    public static List<Long> relationList(Twitter tw, long userId, FriendsFollowers dir, javax.swing.JLabel jlabel) throws TwitterException, InterruptedException{
        List<Long> retList=new ArrayList<>();
        long cursor=-1;
        IDs ids;

        do{
            if(dir==FriendsFollowers.FOLLOWERS){
                ids=tw.getFollowersIDs(userId, cursor);
            }else{
                ids=tw.getFriendsIDs(userId, cursor);
            }
            jlabel.setText(String.format("user %d's %s: page %d",userId,dir,cursor));
            for(long l : ids.getIDs()){
                retList.add(l);
            }
            cursor=ids.getNextCursor();
            sleepSupressWarnings(1000);
        }while(cursor!=0 && cursor<=20);
        
        return retList;
    }
        
    public static boolean isUserIdPreventedInXML(long userId, javax.swing.JTextArea textArea){
        // is already rejected
        boolean ret = isDuplicatedUserIdInXMLUserList(userId, TwitterWatchFollow.m_followerHandler.getM_documentRoot().getRejected().getUser());
        if(ret){
            textArea.append("already 'rejected'");
            return ret;
        }
        // is already paused
        ret = isDuplicatedUserIdInXMLUserList(userId, TwitterWatchFollow.m_followerHandler.getM_documentRoot().getPaused().getUser());
        if(ret){
            textArea.append("already 'paused'");
            return ret;
        }
        // is already in friendship
        ret = isDuplicatedUserIdInXMLUserList(userId, TwitterWatchFollow.m_followerHandler.getM_documentRoot().getCurrentFriends().getUser());
        if(ret){
            textArea.append("already 'friends'");
            return ret;
        }
        return ret;
    }
    
    
    public static double getFriendShipRate(xjcConf.ApplicationType appli, Twitter tw, xjcFollower.User curuser, User curshowuser, javax.swing.JTextArea textArea) throws TwitterException, InterruptedException{
        Calendar cal=Calendar.getInstance();
        cal.add(Calendar.DAY_OF_MONTH, -7);
        Date basedate=cal.getTime();
//        // is not rejected
//        for(xjcFollower.User userelem : TwitterWatchFollow.m_followerHandler.getM_documentRoot().getRejected().getUser()){
//            if(userelem.getValue()==curuser.getValue()){
//                textArea.append("already 'rejected'");
//                return -1.0;
//            }
//        }
//        // is not paused
//        for(xjcFollower.User userelem : TwitterWatchFollow.m_followerHandler.getM_documentRoot().getPaused().getUser()){
//            if(userelem.getValue()==curuser.getValue()){
//                textArea.append("already 'paused'");
//                return -1.0;
//            }
//        }
        // is not protected
        if(curshowuser.isProtected()){
            textArea.append("protected user");
            return -1.0;
        }
        // is not bot
        Pattern botPtn=Pattern.compile("_bot", Pattern.CASE_INSENSITIVE);
        String fooName=curuser.getName();
        textArea.append(String.format("%s ",fooName));
        if(botPtn.matcher(fooName).find()){
            textArea.append("bot");
            return -1.0;
        }
        String fooScreenName=curuser.getScreenName();
        if(botPtn.matcher(fooScreenName).find()){
            textArea.append("bot");
            return -1.0;
        }
        textArea.append(String.format("(%s) ",fooScreenName));
        // regex allow/deny xml restriction
        if(!isAllowDenyXML(fooName)){
            textArea.append(String.format("Name %s is ignore pattern",fooName));
            return -1.0;
        }
        if(!isAllowDenyXML(fooScreenName)){
            textArea.append(String.format("ScreenName %s is ignore pattern",fooScreenName));
            return -1.0;
        }
        System.out.println("AllowDeny done.");
        System.out.flush();
        // is not blocked
        // + can not implement this method.
        // is ja
        if(!curshowuser.getLang().equalsIgnoreCase("ja")){
            textArea.append("Lang not 'ja'");
            return -1.0;
        }
//        // is not already in friendship
//        for(xjcFollower.User userelem : TwitterWatchFollow.m_followerHandler.getM_documentRoot().getCurrentFriends().getUser()){
//            if(userelem.getValue()==curuser.getValue()){
//                textArea.append("already 'friends'");
//                return -1.0;
//            }
//        }
        if(curshowuser.isFollowRequestSent()) return -1.0;
        // friends min/max
        int friendsCount=curshowuser.getFriendsCount();
        if(friendsCount<TwitterWatchFollow.m_friends_count_min){
            textArea.append(String.format("friends=%d <%d",friendsCount,TwitterWatchFollow.m_friends_count_min));
            return -1.0;
        }
        if(friendsCount>TwitterWatchFollow.m_friends_count_max){
            textArea.append(String.format("friends=%d >%d",friendsCount,TwitterWatchFollow.m_friends_count_max));
            return -1.0;
        }
        // friends/followers ratio
        int followersCount=curshowuser.getFollowersCount();
        double rate=(double) friendsCount/followersCount;
        if(rate<TwitterWatchFollow.m_friends_followers_ratio_min){
            textArea.append(String.format("friend/follower=%.2f <%.2f",rate,TwitterWatchFollow.m_friends_followers_ratio_min));
            return -1.0;
        }else if(rate>TwitterWatchFollow.m_friends_followers_ratio_max){
            textArea.append(String.format("friend/follower=%.2f >%.2f",rate,TwitterWatchFollow.m_friends_followers_ratio_max));
            return -1.0;
        }
        // average update message in a week
        int pagenumber=1;
        int count=20;
        long timelineMax=Math.round(TwitterWatchFollow.m_post_a_day_max*(TwitterWatchFollow.m_cumulative_days+1));
        textArea.append(String.format("%1d",pagenumber));
        Paging page=new Paging(pagenumber, count);
        ResponseList userTimeline = tw.getUserTimeline(curuser.getValue(), page);
        if(userTimeline.size()==0){
            textArea.append("no Timeline Statuses");
            return -1.0;
        }
        // is bot (second effort)
        int sourceBotCount=0;
        Pattern botSrcPtn=Pattern.compile("bot",Pattern.CASE_INSENSITIVE);
        Pattern botSharpPtn=Pattern.compile("#[A-Za-z]");
        for(int i=0;i<3;++i){
            if(i>=userTimeline.size()) break;
            Status stat=(Status) userTimeline.get(i);
            String source=stat.getSource();
            if(botSrcPtn.matcher(source).find()) ++sourceBotCount;
            String tweet=stat.getText();
            Matcher botSharpMatch = botSharpPtn.matcher(tweet);
            if(botSharpMatch.groupCount()>=3){
                textArea.append(" tweet guessed 'bot' ");
                return -1.0;
            }
        }
        if(sourceBotCount>0){
            textArea.append(" source is 'bot' ");
            return -1.0;
        }
        //
        int prevTimelineSize=-1;
        while(((Status) userTimeline.get(userTimeline.size()-1)).getCreatedAt().after(basedate)){
            sleepSupressWarnings(1000);
            textArea.append(String.format("%1d",++pagenumber));
            page.setPage(pagenumber);
            userTimeline.addAll(tw.getUserTimeline(curuser.getValue(), page));
            System.out.printf("current page=%d, timelineList.Size=%d, max=%d\n",pagenumber,userTimeline.size(),timelineMax);
            int userTimelineSize=userTimeline.size();
            if(userTimelineSize>=timelineMax || userTimelineSize==prevTimelineSize || pagenumber>9) break;
            prevTimelineSize=userTimelineSize;
        }
        int updateCountAfterBaseDate=userTimeline.size();
        for(int i=userTimeline.size()-1;i>=0;--i){
            Status stat=(Status) userTimeline.get(i);
            // System.out.printf("%d (%s) in %s : %s\n",stat.getId(),stat.getUser().getScreenName(),stat.getCreatedAt(), stat.getText());
            if(stat.getCreatedAt().before(basedate)) --updateCountAfterBaseDate;
            else break;
        }
        double updateRate=updateCountAfterBaseDate/TwitterWatchFollow.m_cumulative_days;
        textArea.append(String.format(" , update %.2f a day ",updateRate));
        System.out.printf("%d %s (%s) update %d in a week (%.2f a day)\n",curuser.getValue(),fooName,fooScreenName,updateCountAfterBaseDate,updateRate);
        return updateRate;
    }

    public static void sleepSupressWarnings(int millsecond) throws InterruptedException {
        Thread.sleep(millsecond);
    }
    
    public static void setWindowCenterPositionAtParent(java.awt.Window parent, java.awt.Window child){
        int pX=parent.getX();
        int pY=parent.getY();
        int pW=parent.getWidth();
        int pH=parent.getHeight();
        int cW=child.getWidth();
        int cH=child.getHeight();
        
        int cX=pX+pW/2-cW/2;
        if(cX<0) cX=0;
        int cY=pY+pH/2-cH/2;
        if(cY<0) cY=0;
        
        child.setBounds(cX,cY,cW,cH);
    }
    
    public static boolean isDuplicatedUserIdInXMLUserList(long newUserId, List<xjcFollower.User> userList) {
        for(xjcFollower.User userelem : userList){
            if(userelem.getValue()==newUserId) return true;
        }
        return false;
    }
    
    /**
     * check whether index of checkedUserList is used in referenceUserList or not, and create List of indices
     * @param checkedUserList userList to be checked
     * @param referenceUserList referenced userList
     * @return List of indices in checkedUserList
     */
    public static List<Integer> duplicatedUserListById(List<xjcFollower.User> checkedUserList, List<xjcFollower.User> referenceUserList){
        List<Integer> duplicateList=new ArrayList<>();
        for(int i=0;i<checkedUserList.size();++i){
            for(int j=0;j<referenceUserList.size();++j){
                if(checkedUserList.get(i).getValue()==checkedUserList.get(j).getValue()){
                    duplicateList.add(i);
                    break;
                }
            }
        }
        return duplicateList;
    }
    
    public static int uniquefyUserListById(List<xjcFollower.User> userList){
        List<Integer> removeList=new ArrayList<>();
        int removeCount=0;
        for(int i=0;i<userList.size()-1;++i){
            for(int j=i+1;j<userList.size();++j){
                if(userList.get(i).getValue()==userList.get(j).getValue()){
                    ++removeCount;
                    removeList.add(j);
                }
            }
        }
        java.util.Collections.sort(removeList);
        java.util.Collections.reverse(removeList);
        for(int r : removeList){
            if(r<0 || r>=userList.size()) continue;
            userList.remove(r);
        }
        return removeCount;
    }
    
    private static boolean isAllowDenyXML(String name) {
        System.out.println("Entering in isAllowDenyXML(String)");
        xjcConf.Root confDocRoot=TwitterWatchFollow.m_confHandler.getM_documentRoot();
        int allowCount=countListInXML(confDocRoot.getIgnoreWord().getWord(),xjcConf.BindType.ALLOW);
        int denyCount=countListInXML(confDocRoot.getIgnoreWord().getWord(),xjcConf.BindType.DENY);
        xjcConf.BindOrderType allowdeny = confDocRoot.getIgnoreWord().getAllowdeny();
        //System.out.println("In isAllowDenyXML(String) step 1");
        if(allowdeny==xjcConf.BindOrderType.ALLOW_TO_DENY && allowCount==0){
            xjcConf.WordType wordelem=new xjcConf.ObjectFactory().createWordType();
            wordelem.setBind(xjcConf.BindType.ALLOW);
            wordelem.setMode(xjcConf.RegexModeType.REGEX);
            wordelem.setValue(".*");
            confDocRoot.getIgnoreWord().getWord().add(wordelem);
        }
        //System.out.println("In isAllowDenyXML(String) step 2");
        if(allowdeny==xjcConf.BindOrderType.DENY_TO_ALLOW && denyCount==0){
            xjcConf.WordType wordelem=new xjcConf.ObjectFactory().createWordType();
            wordelem.setBind(xjcConf.BindType.DENY);
            wordelem.setMode(xjcConf.RegexModeType.REGEX);
            wordelem.setValue(".*");
            confDocRoot.getIgnoreWord().getWord().add(wordelem);
        }
        //System.out.println("In isAllowDenyXML(String) step 3");
        int allowMatchCount=allow_denyStringMatch(name,confDocRoot.getIgnoreWord().getWord(),xjcConf.BindType.ALLOW);
        int denyMatchCount=allow_denyStringMatch(name,confDocRoot.getIgnoreWord().getWord(),xjcConf.BindType.DENY);
        System.out.printf("%s allow/deny=%d/%d\n",name,allowMatchCount,denyMatchCount);
        System.out.println("In isAllowDenyXML(String) almost done");
        System.out.flush();
        if(allowMatchCount>0 && denyMatchCount==0) return true;
        return false;
    }
    
    private static int countListInXML(List<xjcConf.WordType> list, xjcConf.BindType bindType) {
        int count=0;
        for(xjcConf.WordType word : list){
            if(word.getBind()==bindType) ++count;
        }
        return count;
    }
    private static int allow_denyStringMatch(String name, List<xjcConf.WordType> wordList, xjcConf.BindType bindType) {
        //System.out.println("Entering in allow_denyStringMatch(String,List<>,xjcConf.BindType)");
        int matchCount=0;
        //System.out.printf("wordList %s\n",wordList);
        if(wordList==null){
            return 0;
        }
        for(int i=0;i<wordList.size();++i){
            //System.out.printf("\tloop %d/%d\n",i+1,wordList.size());
            //System.out.flush();
            xjcConf.WordType wordelem=wordList.get(i);
            if(wordelem==null) continue;
            if(wordelem.getBind()!=bindType) continue;
            String wordValue=wordelem.getValue();
            if(wordValue==null) continue;
            if(wordelem.getMode()==null) wordelem.setMode(xjcConf.RegexModeType.NORMAL);
            if(wordelem.isIgnoreCase()==null) wordelem.setIgnoreCase(Boolean.FALSE);
            boolean ignoreCase=true;
            if(wordelem.isIgnoreCase()==null || !wordelem.isIgnoreCase()) ignoreCase=false;
            switch(wordelem.getMode()){
            case REGEX:
                Pattern ptn;
                if(ignoreCase){
                    ptn=Pattern.compile(wordValue,Pattern.CASE_INSENSITIVE);
                }else{
                    ptn=Pattern.compile(wordValue);
                }
                if(ptn.matcher(name).find()) ++matchCount;
                break;
            default:
                if(ignoreCase){
                    if(name.toUpperCase().contains(wordValue.toUpperCase())) ++matchCount;
                }else{
                    if(name.contains(wordValue)) ++matchCount;
                }
            }
        }
        //System.out.println("Leaving from allow_denyStringMatch(String,List<>,xjcConf.BindType)");
        //System.out.flush();
        return matchCount;
    }
    
    public static boolean sleepUntilRateLimitReset(TwitterException ex, javax.swing.JFrame rootJFrame) throws InterruptedException {
        System.out.println("catching exception: code="+ex.getStatusCode());
        if(ex.getStatusCode()==400){
            final int sleepInSecond=ex.getRetryAfter()+60*3;
            System.out.println("waiting until rate reset: "+sleepInSecond+" sec...");
            //
            //Thread.sleep((ex.getRetryAfter()+60*3)*1000);
            final RetrySleepJDialog dlg=new RetrySleepJDialog(rootJFrame, false, sleepInSecond);
            TwitterCommon.setWindowCenterPositionAtParent(rootJFrame, dlg);
            dlg.setVisible(true);
            //
            Callable<Boolean> task = new Callable<Boolean>(){
                public Boolean call(){
                    try {
                        int i=0;
                        while(!dlg.isStop() && ++i<sleepInSecond){
                            Thread.sleep(1000);
                            //System.out.println("looping="+i); System.out.flush();
                        }
                    } catch (InterruptedException e){}
                    return dlg.isStop();
                }
            };
            
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Future<Boolean> future = executor.submit(task);
            System.out.println("execute: future"); System.out.flush();
            try {
                Boolean ret = future.get(sleepInSecond, TimeUnit.SECONDS);
                System.out.println("execute-done: ret="+ret); System.out.flush();
            } catch (TimeoutException e) {
                System.out.println("catch TimeoutException"); System.out.flush();
            } catch (InterruptedException e) {
                System.out.println("catch InterruptedException"); System.out.flush();
            } catch (ExecutionException e){
                System.out.println("catch ExecutionException"); System.out.flush();
            }
            
            dlg.setVisible(false);
            System.out.println("Leaving future method"); System.out.flush();
            return !dlg.isStop();
        }else if(ex.getStatusCode()==403 || ex.getStatusCode()==404){
            Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("This exception is skipped.");
            return true;
        }
        ex.printStackTrace();
        System.out.flush();
        System.err.flush();
        System.out.println("This exception is unhandled. Exception occures!!");
        return false;
    }

    public static Object deepCopy(Object o) throws IOException, ClassNotFoundException{
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(o);
        
        byte[] buff = baos.toByteArray();
        
        ByteArrayInputStream bais = new ByteArrayInputStream(buff);
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object copy = ois.readObject();
        return copy;
    }

    public static List<Integer> createRandIndexList(int size) {
        List<Integer> retlist = new ArrayList<>();
        for(int i=0;i<size;++i){
            retlist.add(i);
        }
        Collections.shuffle(retlist);
        return retlist;
    }

    static String getReplyMessage(ReplyOrderType replyOrderType, String actmsg) {
        List<String> candlist = new ArrayList<>();
        String ret=null;
        for( xjcConf.MessageType replyMessage : TwitterWatchFollow.m_confHandler.getM_documentRoot().getReplyMessage()){
            if(replyMessage.getOrder()!=replyOrderType) continue;
            if(actmsg==null || actmsg.length()==0){
               for(ReplyMessageType messageElem : replyMessage.getMessage()){
                    candlist.add(messageElem.getValue());
                }
            }else{
               for(ReplyMessageType messageElem : replyMessage.getMessage()){
                   String ptnstr=messageElem.getPattern();
                   if(ptnstr==null || ptnstr.length()==0){
                       candlist.add(messageElem.getValue());
                   }else{
                       Pattern ptn=Pattern.compile(ptnstr);
                       if(ptn.matcher(actmsg).find()){
                           candlist.add(messageElem.getValue());
                       }
                   }
                }
            }
        }
        //
        if(candlist.isEmpty()) return null;
        ret=candlist.get((int) (Math.random()*((double) candlist.size())));
        return ret;
    }
}
